// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/module.h>
#include <linux/component.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <drm/drmP.h>

#include "lombo_doss.h"

#define LOMBO_DOSS_MAX_PATH 2

enum {
	LOMBO_DOSS_SCLK0 = 0,
	LOMBO_DOSS_SCLK1,
	LOMBO_DOSS_HFEOSC,
	LOMBO_DOSS_SCLK_NUM,
};

struct lombo_doss_top;

struct lombo_doss_sclk {
	struct clk *clk;
	struct clk *parent_clk;
	uint32_t src_id; /* clock source id */
};

struct lombo_doss_top {
	uint32_t id;

	struct clk *ahb_gate;
	struct clk *ahb_reset;

	struct lombo_doss_sclk sclk[LOMBO_DOSS_SCLK_NUM];
	struct list_head clk_list;

	void __iomem *reg;

	/* for doss top access */
	spinlock_t slock;
	struct mutex mlock;
	int en_cnt; /* host en_cnt */
};

struct lombo_doss_clk_priv {
	struct lombo_doss_clk base;
	struct lombo_doss_top *doss_top;
	struct lombo_doss_sclk *sclk;
	struct kref ref_cnt;
	struct list_head list;
	struct mutex mlock;
	int en_cnt;
};

int lombo_doss_route(doss_dc_tcon_route_t route)
{
	csp_doss_set_input_route(route);
	return 0;
}
EXPORT_SYMBOL(lombo_doss_route);

static inline struct lombo_doss_clk_priv *to_lombo_doss_clk_priv(
	struct lombo_doss_clk *clk)
{
	return container_of(clk, struct lombo_doss_clk_priv, base);
}

static inline void lombo_doss_do_sync_clk_ref(int top_id,
	struct lombo_doss_sclk *sclk, int path_id, int clk_id)
{
	if (csp_doss_clk_is_enable(top_id, path_id, clk_id))
		clk_prepare_enable(sclk->clk);
}

struct lombo_doss_clk *lombo_doss_get_clk(
	struct device *dev, struct device *doss_top,
	uint32_t path_id, doss_clk_id_t clk_id)
{
	struct lombo_doss_top *top = dev_get_drvdata(doss_top);
	struct lombo_doss_clk *clk = NULL;
	struct lombo_doss_sclk *sclk;
	struct lombo_doss_clk_priv *priv = NULL;
	uint32_t sclk_id = LOMBO_DOSS_SCLK_NUM;

	if (path_id == 0) {
		switch (clk_id) {
		case DOSS_CLK_TCON:
		case DOSS_CLK_LVDS:
		case DOSS_CLK_MIPI_DSI_REF:
			sclk_id = LOMBO_DOSS_SCLK0;
			break;
		case DOSS_CLK_EDP_AUX:
		case DOSS_CLK_EDP_REF:
		case DOSS_CLK_MIPI_DSI_CFG:
			sclk_id = LOMBO_DOSS_HFEOSC;
			break;
		default:
			break;
		}
	} else if (path_id == 1) {
		switch (clk_id) {
		case DOSS_CLK_TCON:
		case DOSS_CLK_LVDS:
		case DOSS_CLK_MIPI_DSI_REF:
			sclk_id = LOMBO_DOSS_SCLK1;
			break;
		case DOSS_CLK_EDP_AUX:
		case DOSS_CLK_EDP_REF:
		case DOSS_CLK_MIPI_DSI_CFG:
			sclk_id = LOMBO_DOSS_HFEOSC;
			break;
		default:
			break;
		}
	}
	if (sclk_id >= LOMBO_DOSS_SCLK_NUM) {
		DRM_ERROR("inval path_id=%d, clk_id=%x, sclk_id=%d\n",
			path_id, clk_id, sclk_id);
		return NULL;
	}

	sclk = &top->sclk[sclk_id];
	if (!sclk->clk) {
		DRM_ERROR("null sclk%d\n", sclk_id);
		return NULL;
	}

	/**
	 * here not use spinlock for clk_list, the reason is:
	 * lombo_dpu_get_clk should be called by moules in driver-probe/bind.
	 * Mouldes are binded in sequence.
	 */
	list_for_each_entry(priv, &top->clk_list, list) {
		clk = &priv->base;
		if ((clk->path_id == path_id)
			&& (clk->clk_id == clk_id)
			&& (clk->dev == dev)) {
			kref_get(&priv->ref_cnt);
			return clk;
		}
	}

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		DRM_ERROR("%d\n", __LINE__);
		return NULL;
	}

	clk = &priv->base;
	priv->doss_top = top;
	clk->dev = dev;
	clk->path_id = path_id;
	clk->clk_id = clk_id;
	priv->sclk = sclk;
	/* init clk rate equal sclk rate */
	clk->real_rate = clk_get_rate(sclk->clk);
	clk->div1 = clk->div0 = 1;
	lombo_doss_do_sync_clk_ref(top->id, sclk, path_id, clk_id);

	kref_init(&priv->ref_cnt);
	mutex_init(&priv->mlock);
	list_add_tail(&priv->list, &top->clk_list);

	return clk;
}
EXPORT_SYMBOL(lombo_doss_get_clk);

static void lombo_doss_clk_free(struct kref *kref)
{
	struct lombo_doss_clk_priv *mod_clk =
		container_of(kref, struct lombo_doss_clk_priv, ref_cnt);

	list_del(&mod_clk->list);
	kfree(mod_clk);
}

int lombo_doss_put_clk(struct lombo_doss_clk **p_clk)
{
	struct lombo_doss_clk_priv *clk;

	if (!(p_clk && (*p_clk))) {
		DRM_ERROR("%d\n", __LINE__);
		return -EINVAL;
	}

	clk = to_lombo_doss_clk_priv(*p_clk);
	kref_put(&clk->ref_cnt, lombo_doss_clk_free);
	*p_clk = NULL;

	return 0;
}
EXPORT_SYMBOL(lombo_doss_put_clk);

unsigned long lombo_doss_clk_get_rate(struct lombo_doss_clk *clk)
{
	if (clk)
		return clk->real_rate;

	return 0;
}
EXPORT_SYMBOL(lombo_doss_clk_get_rate);

int lombo_doss_clk_set_rate(struct lombo_doss_clk *clk, unsigned long rate)
{
	if (!clk || (rate == 0)) {
		DRM_ERROR("clk=%p, rate=%lu\n", clk, rate);
		return -EINVAL;
	}
	clk->req_rate = rate;
	return 0;
}
EXPORT_SYMBOL(lombo_doss_clk_set_rate);

unsigned int lombo_doss_clk_get_div(struct lombo_doss_clk *clk)
{
	return clk->div0 * clk->div1;
}
EXPORT_SYMBOL(lombo_doss_clk_get_div);

static inline void lombo_csp_doss_init(
	struct lombo_doss_top *top, uint8_t sw)
{
	int en_cnt;

	spin_lock(&top->slock);
	top->en_cnt++;
	en_cnt = top->en_cnt;
	if ((en_cnt == 1) && !sw)
		csp_doss_init(top->id);
	spin_unlock(&top->slock);

	DRM_DEBUG_KMS("%s: en_cnt=%d, sw=%d\n", __func__, en_cnt, sw);
}

static inline void lombo_csp_doss_exit(struct lombo_doss_top *top)
{
	int en_cnt;

	spin_lock(&top->slock);
	top->en_cnt--;
	en_cnt = top->en_cnt;
	if (top->en_cnt == 0)
		csp_doss_exit(top->id);
	spin_unlock(&top->slock);

	DRM_DEBUG_KMS("%s: en_cnt=%d\n", __func__, en_cnt);
}

int lombo_doss_path_set(struct device *dev_top,
	int path_id, doss_ctrl_id_t ctrl_id, int enable)
{
	struct lombo_doss_top *top = dev_get_drvdata(dev_top);

	if (enable) {
		csp_doss_path_init(top->id, path_id, ctrl_id);
		csp_doss_path_enable(top->id, path_id, ctrl_id);
	} else {
		csp_doss_path_disable(top->id, path_id, ctrl_id);
		csp_doss_path_exit(top->id, path_id, ctrl_id);
	}

	return 0;
}
EXPORT_SYMBOL(lombo_doss_path_set);

static unsigned long lombo_doss_get_best_sclk_rate_rgb(
	struct lombo_doss_clk *clk, unsigned long *best_rate)
{
	struct lombo_doss_clk_priv *priv = to_lombo_doss_clk_priv(clk);
	struct lombo_doss_sclk *sclk = priv->sclk;
	uint64_t tmp;
	uint32_t rem;
	unsigned long __best_rate;

	if (!__clk_is_enabled(sclk->parent_clk))
		clk_prepare_enable(sclk->parent_clk);
	tmp = clk_get_rate(sclk->parent_clk);
	rem = do_div(tmp, clk->req_rate);
	if ((tmp == 0) || (rem != 0) || (tmp > DOSS_CLK_DIV_MAX0)) {
		unsigned long req_rate = clk->req_rate, actual_rate;
		uint32_t i = 1, min = UINT_MAX, diff = UINT_MAX;

		__best_rate = req_rate;
		for (; i <= DOSS_CLK_DIV_MAX0; i++) {
			tmp = clk_round_rate(
				sclk->parent_clk, req_rate * i);
			if (tmp == -EINVAL)
				continue;
			actual_rate = tmp;
			do_div(tmp, i);
			if (req_rate == tmp) {
				__best_rate = actual_rate;
				break;
			}
			diff = abs(req_rate - tmp);
			if (diff < min) {
				min = diff;
				__best_rate = actual_rate;
			}
		}
	} else {
		__best_rate = tmp * clk->req_rate;
	}

	*best_rate = __best_rate;
	return clk_get_rate(sclk->parent_clk);
}

static unsigned long lombo_doss_get_best_sclk_rate_lvds(
	struct lombo_doss_clk *clk, unsigned long *best_rate)
{
	struct lombo_doss_clk_priv *priv = to_lombo_doss_clk_priv(clk);
	struct lombo_doss_sclk *sclk = priv->sclk;
	unsigned long __best_rate, lvds_req_rate;

	if (!__clk_is_enabled(sclk->parent_clk))
		clk_prepare_enable(sclk->parent_clk);

	lvds_req_rate = (clk->clk_id == DOSS_CLK_TCON) ?
		clk->req_rate * 7 : clk->req_rate;
	__best_rate = clk_round_rate(sclk->parent_clk, lvds_req_rate);

	*best_rate = __best_rate;
	return clk_get_rate(sclk->parent_clk);
}

static unsigned long lombo_doss_get_best_sclk_rate_dsi(
	struct lombo_doss_clk *clk, unsigned long *best_rate)
{
	struct lombo_doss_clk_priv *priv = to_lombo_doss_clk_priv(clk);
	struct lombo_doss_sclk *sclk = priv->sclk;
	unsigned long __best_rate;

	if (!__clk_is_enabled(sclk->parent_clk))
		clk_prepare_enable(sclk->parent_clk);

	if (clk->req_rate < DOSS_TCON_DCLK_MID) {
		__best_rate = DOSS_SCLK_FREQ_LOW;
	} else {
		uint64_t tmp = DOSS_SCLK_FREQ_HIGH;
		uint32_t rem;

		rem = do_div(tmp, clk->req_rate);
		if (rem)
			DRM_WARN("req_rate can't be able to div sclk evenly\n");
		__best_rate = DOSS_SCLK_FREQ_HIGH;
	}

	*best_rate = __best_rate;
	return clk_get_rate(sclk->parent_clk);
}

unsigned long lombo_doss_get_best_sclk_rate(struct lombo_doss_clk *clk,
	doss_clk_id_t clk_id, unsigned long *best_rate)
{
	switch (clk_id) {
	case DOSS_CLK_TCON:
	case DOSS_CLK_EDP_REF:
	case DOSS_CLK_EDP_AUX:
		return lombo_doss_get_best_sclk_rate_rgb(clk, best_rate);
	case DOSS_CLK_LVDS:
		return lombo_doss_get_best_sclk_rate_lvds(clk, best_rate);
	case DOSS_CLK_MIPI_DSI_REF:
	case DOSS_CLK_MIPI_DSI_CFG:
		return lombo_doss_get_best_sclk_rate_dsi(clk, best_rate);
	case DOSS_CLK_CSI_TX:
	case DOSS_CLK_CSI_TX_GAT:
	case DOSS_CLK_TVE:
	case DOSS_CLK_HDMI_REF:
	case DOSS_CLK_HDMI_SFR:
	case DOSS_CLK_HDMI_CEC:
	case DOSS_CLK_VGA:
		DRM_DEBUG_KMS("need fix for clk[0x%x]\n", clk->clk_id);
		return 0;
	default:
		DRM_ERROR("invalid doss clk id[0x%x]", clk->clk_id);
		return -EINVAL;
	}
}
EXPORT_SYMBOL(lombo_doss_get_best_sclk_rate);

int lombo_doss_set_sclk_rate(struct lombo_doss_clk *clk, unsigned long rate)
{
	unsigned long __rate = 0;
	struct lombo_doss_clk_priv *priv = to_lombo_doss_clk_priv(clk);
	struct lombo_doss_sclk *__sclk = priv->sclk;
	struct clk *sclk = __sclk->parent_clk;

	if (!__clk_is_enabled(sclk))
		clk_prepare_enable(sclk);

	if (!rate)
		clk_disable_unprepare(sclk);

	__rate = clk_get_rate(sclk);
	if (__rate != rate)
		clk_set_rate(sclk, rate);
	DRM_DEBUG_KMS("reconfig sclk%d @ %luHz\n", clk->path_id, rate);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_set_sclk_rate);

int lombo_doss_top_enable(struct device *dev_top, int sw)
{
	int en_cnt;
	struct lombo_doss_top *top = dev_get_drvdata(dev_top);

	mutex_lock(&top->mlock);
	top->en_cnt++;
	en_cnt = top->en_cnt;
	if (en_cnt != 1)
		goto out;

	if (!sw) {
		clk_prepare_enable(top->ahb_gate);
		clk_prepare_enable(top->ahb_reset);
		csp_doss_init(top->id);
	}

out:
	mutex_unlock(&top->mlock);
	WARN_ON(en_cnt <= 0);
	DRM_DEBUG_KMS("doss_top%d en_cnt=%d\n", top->id, top->en_cnt);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_top_enable);

int lombo_doss_top_disable(struct device *dev_top)
{
	int en_cnt;
	struct lombo_doss_top *top = dev_get_drvdata(dev_top);

	mutex_lock(&top->mlock);
	top->en_cnt--;
	en_cnt = top->en_cnt;
	if (en_cnt != 0)
		goto out;

	csp_doss_exit(top->id);
	clk_disable_unprepare(top->ahb_reset);
	clk_disable_unprepare(top->ahb_gate);

out:
	mutex_unlock(&top->mlock);
	WARN_ON(en_cnt < 0);
	DRM_DEBUG_KMS("doss_top%d en_cnt=%d\n",	top->id, top->en_cnt);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_top_disable);

static inline doss_ctrl_id_t lombo_doss_get_ctrl_id_by_clk(
	struct lombo_doss_clk *clk)
{
	doss_ctrl_id_t ctrl_id = DOSS_CTRL_TCON;

	switch (clk->clk_id) {
	case DOSS_CLK_TCON:
		ctrl_id = DOSS_CTRL_TCON;
		break;
	case DOSS_CLK_LVDS:
		ctrl_id = DOSS_CTRL_LVDS;
		break;
	case DOSS_CLK_MIPI_DSI_REF:
	case DOSS_CLK_MIPI_DSI_CFG:
		ctrl_id = DOSS_CTRL_MIPI_DSI;
		break;
	case DOSS_CLK_EDP_REF:
	case DOSS_CLK_EDP_AUX:
		ctrl_id = DOSS_CTRL_EDP;
		break;
	case DOSS_CLK_CSI_TX:
	case DOSS_CLK_CSI_TX_GAT:
		ctrl_id = DOSS_CTRL_CSI_TX;
		break;
	case DOSS_CLK_TVE:
		ctrl_id = DOSS_CTRL_TVE;
		break;
	case DOSS_CLK_HDMI_REF:
	case DOSS_CLK_HDMI_SFR:
	case DOSS_CLK_HDMI_CEC:
		ctrl_id = DOSS_CTRL_HDMI;
		break;
	case DOSS_CLK_VGA:
		ctrl_id = DOSS_CTRL_VGA;
		break;
	default:
		DRM_ERROR("invalid doss clk id[%d]", clk->clk_id);
		break;
	}

	return ctrl_id;
}

int lombo_doss_clk_enable(struct lombo_doss_clk *clk, int sw)
{
	int en_cnt, div;
	unsigned long real_rate;
	uint64_t tmp;
	struct lombo_doss_clk_priv *priv = to_lombo_doss_clk_priv(clk);
	struct lombo_doss_top *top = priv->doss_top;

	mutex_lock(&priv->mlock);
	priv->en_cnt++;
	en_cnt = priv->en_cnt;
	if ((en_cnt != 1) || sw)
		goto out;

	clk_prepare_enable(priv->sclk->clk);
	real_rate = clk_get_rate(priv->sclk->clk);
	tmp = real_rate;
	div = lombo_doss_clk_get_div(clk);
	do_div(tmp, div);
	clk->real_rate = tmp;
	csp_doss_clk_set(top->id, clk->path_id, clk->clk_id,
			priv->sclk->src_id, clk->div0 - 1, clk->div1 - 1);
	csp_doss_clk_enable(top->id, clk->path_id, clk->clk_id);
out:
	mutex_unlock(&priv->mlock);
	WARN_ON(en_cnt <= 0);
	DRM_DEBUG_KMS(
		"clk[0x%x] en_cnt=%d req_rate=%lu real_rate=%lu div[%d %d]\n",
		clk->clk_id, priv->en_cnt, clk->req_rate, clk->real_rate,
		clk->div0, clk->div1);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_clk_enable);

int lombo_doss_clk_disable(struct lombo_doss_clk *clk)
{
	int en_cnt;
	struct lombo_doss_clk_priv *priv = to_lombo_doss_clk_priv(clk);
	struct lombo_doss_top *top = priv->doss_top;
	doss_ctrl_id_t ctrl_id = lombo_doss_get_ctrl_id_by_clk(clk);

	mutex_lock(&priv->mlock);
	priv->en_cnt--;
	en_cnt = priv->en_cnt;
	if (en_cnt != 0)
		goto out;

	csp_doss_path_disable(top->id, clk->path_id, ctrl_id);
	csp_doss_path_exit(top->id, clk->path_id, ctrl_id);
	csp_doss_clk_disable(top->id, clk->path_id, clk->clk_id);

	clk_disable_unprepare(priv->sclk->clk);
	clk->real_rate = 0;

out:
	mutex_unlock(&priv->mlock);
	WARN_ON(en_cnt < 0);
	DRM_DEBUG_KMS("clk[0x%x] en_cnt=%d real_rate=%lu\n",
		clk->clk_id, priv->en_cnt, clk->real_rate);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_clk_disable);

int lombo_doss_vref_config(struct device *doss_top, int path_id,
	doss_ctrl_id_t ctrl_id, int en)
{
	struct lombo_doss_top *top = dev_get_drvdata(doss_top);

	if (en)
		csp_doss_vref_enable(top->id, path_id, ctrl_id);
	else
		csp_doss_vref_disable(top->id, path_id, ctrl_id);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_vref_config);

int lombo_doss_slave_config(struct device *doss_top, int path_id,
	doss_ctrl_id_t ctrl_id, int en)
{
	struct lombo_doss_top *top = dev_get_drvdata(doss_top);

	if (en)
		csp_doss_slave_enable(top->id, path_id, ctrl_id);
	else
		csp_doss_slave_disable(top->id, path_id, ctrl_id);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_slave_config);

int lombo_doss_phy_slave_config(struct device *doss_top, int path_id,
	doss_ctrl_id_t ctrl_id, int en)
{
	struct lombo_doss_top *top = dev_get_drvdata(doss_top);

	if (en)
		csp_doss_phy_slave_enable(top->id, path_id, ctrl_id);
	else
		csp_doss_phy_slave_disable(top->id, path_id, ctrl_id);

	return 0;
}
EXPORT_SYMBOL(lombo_doss_phy_slave_config);

static int lombo_doss_bind(struct device *comp,
	struct device *master, void *master_data)
{
	struct platform_device *pdev;
	struct device *dev = comp;

	struct lombo_doss_top *doss_top;
	struct resource *res;
	int i;
	u32 value = 0;

	DRM_DEBUG_DRIVER("%d\n", __LINE__);
	pdev = to_platform_device(comp);
	doss_top = kzalloc(sizeof(*doss_top), GFP_KERNEL);
	if (!doss_top) {
		dev_err(dev, "Failed to alloc for doss top\n");
		return -ENOMEM;
	}

	/* parse hardware index */
	if (!of_property_read_u32(dev->of_node, "hw_id", &value))
		doss_top->id = value;
	else
		DRM_DEBUG("get doss hw_id failed\n");

	/* parse bus clks */
	doss_top->ahb_gate = devm_clk_get(dev, "ahb_gate");
	if (IS_ERR(doss_top->ahb_gate)) {
		dev_err(dev, "failed to get doss ahb_gate\n");
		return -ENODEV;
	}
	if (__clk_is_enabled(doss_top->ahb_gate))
		clk_prepare_enable(doss_top->ahb_gate);

	doss_top->ahb_reset = devm_clk_get(dev, "ahb_reset");
	if (IS_ERR(doss_top->ahb_reset)) {
		dev_err(dev, "failed to get doss ahb_reset\n");
		return -ENODEV;
	}
	if (__clk_is_enabled(doss_top->ahb_reset))
		clk_prepare_enable(doss_top->ahb_reset);

	/* parse sclk & psclk */
	for (i = 0; i < LOMBO_DOSS_SCLK_NUM; i++) {
		/* clk name sequence must according enum doss_sclk_id */
		const char *name[][3] = {
			{ "sclk0", "psclk0" },
			{ "sclk1", "psclk1" },
			{ "hfeosc", NULL },
		};
		struct lombo_doss_sclk *sclk;

		sclk = &doss_top->sclk[i];
		sclk->clk = devm_clk_get(dev, name[i][0]);
		if (IS_ERR_OR_NULL(sclk->clk)) {
			sclk->clk = NULL; /* null mean invalid sclk */
			continue;
		}
		if (name[i][1]) {
			sclk->parent_clk = devm_clk_get(dev, name[i][1]);
			if (IS_ERR_OR_NULL(sclk->parent_clk)) {
				dev_err(dev, "failed to get clock %s\n",
					name[i][1]);
				clk_put(sclk->clk);
				sclk->clk = NULL;
				sclk->parent_clk = NULL;
				continue;
			}
			clk_set_parent(sclk->clk, sclk->parent_clk);
		}
		switch (i) {
		case LOMBO_DOSS_SCLK0:
			sclk->src_id = DOSS_CLK_SCLK0;
			break;
		case LOMBO_DOSS_SCLK1:
			sclk->src_id = DOSS_CLK_SCLK1;
			break;
		case LOMBO_DOSS_HFEOSC:
			sclk->src_id = DOSS_CLK_HFEOSC;
			break;
		default:
			dev_err(dev, "invalid sclk 0x%x\n", sclk->src_id);
			break;
		}
	}

	/* parse reg addr */
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(dev, "get memory resource failed.\n");
		return -ENXIO;
	}
	doss_top->reg = devm_ioremap(dev, res->start,
				resource_size(res));
	if (doss_top->reg == NULL) {
		dev_err(dev, "register mapping failed.\n");
		return -ENXIO;
	}
	csp_doss_set_register_base(doss_top->id, doss_top->reg,
		(u32)resource_size(res));

	INIT_LIST_HEAD(&doss_top->clk_list);
	spin_lock_init(&doss_top->slock);
	mutex_init(&doss_top->mlock);

	dev_set_drvdata(dev, doss_top);

	return 0;
}

static void lombo_doss_unbind(struct device *comp,
	struct device *master, void *master_data)
{
	struct lombo_doss_top *doss_top = dev_get_drvdata(comp);

	DRM_DEBUG_DRIVER("%d\n", __LINE__);

	csp_doss_set_register_base(0, NULL, 0);
	dev_set_drvdata(comp, NULL);
	kfree(doss_top);
}

static const struct component_ops lombo_doss_ops = {
	.bind	= lombo_doss_bind,
	.unbind	= lombo_doss_unbind,
};

static int lombo_doss_probe(struct platform_device *pdev)
{
	return component_add(&pdev->dev, &lombo_doss_ops);
}

static int lombo_doss_remove(struct platform_device *pdev)
{
	component_del(&pdev->dev, &lombo_doss_ops);

	return 0;
}

static const struct of_device_id lombo_doss_of_table[] = {
	{ .compatible = "lombo,lombo-n5v1-doss" },
	{ .compatible = "lombo,lombo-n7v1-doss" },
	{ .compatible = "lombo,lombo-n7v3-doss" },
	{ .compatible = "lombo,lombo-n7v5-doss" },
	{ .compatible = "lombo,lombo-n9v1-doss" },
	{ }
};
MODULE_DEVICE_TABLE(of, lombo_doss_of_table);

static struct platform_driver lombo_doss_platform_driver = {
	.probe		= lombo_doss_probe,
	.remove		= lombo_doss_remove,
	.driver		= {
		.owner = THIS_MODULE,
		.name		= "lombo-doss",
		.of_match_table	= lombo_doss_of_table,
	},
};
module_platform_driver(lombo_doss_platform_driver);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Lombo SoC DOSS TOP Driver");
MODULE_LICENSE("GPL");
